#!/usr/bin/python3
import os
import sys
import glob
from threading import Thread
import subprocess
import multiprocessing
from shutil import which

if sys.version_info[0] < 3:
        raise Exception("Python 3 or a more recent version is required.")

if (len(sys.argv) < 3):
    sys.exit("We need two arguments. Location of LockStepRunner and folder containing the tests")

# Remove our SHM regions if they still exist
SHM_Files = glob.glob("/dev/shm/*_Lockstep")
for file in SHM_Files:
    os.remove(file)

UnitTests = sorted(glob.glob(sys.argv[2] + "*"))
UnitTestsSize = len(UnitTests)
Threads = [None] * UnitTestsSize
Results = [None] * UnitTestsSize
ThreadResults = [[None] * 2] * UnitTestsSize
MaxFileNameStringLen = 0

def Threaded_Runner(Args, ID, Client):
    Log = open("Log_" + str(ID) + "_" + str(Client), "w")
    Log.write("Args: %s\n" % " ".join(Args))
    Log.flush()
    Process = subprocess.Popen(Args, stdout=Log, stderr=Log)
    Process.wait()
    Log.flush()
    ThreadResults[ID][Client] = Process.returncode

def Threaded_Manager(Runner, ID, File):
    ServerArgs = ["catchsegv", Runner, "-c", "vm", "-n", "1", "-I", "R" + str(ID), File]
    ClientArgs = ["catchsegv", Runner, "-c", "vm", "-n", "1", "-I", "R" + str(ID), "-C"]

    if which("catchsegv") is None:
        ServerArgs.pop(0)
        ClientArgs.pop(0)

    ServerThread = Thread(target = Threaded_Runner, args = (ServerArgs, ID, 0))
    ClientThread = Thread(target = Threaded_Runner, args = (ClientArgs, ID, 1))

    ServerThread.start()
    ClientThread.start()

    ClientThread.join()
    ServerThread.join()

    # The server is the one we should listen to for results
    if (ThreadResults[ID][1] != 0 and ThreadResults[ID][0] == 0):
        # If the client died for some reason but server thought we were fine then take client data
        Results[ID] = ThreadResults[ID][1]
    else:
        # Else just take the server data
        Results[ID] = ThreadResults[ID][0]

    DupLen = MaxFileNameStringLen - len(UnitTests[ID])

    if (Results[ID] == 0):
        print("\t'%s'%s - PASSED ID: %d - 0" % (UnitTests[ID], " "*DupLen, ID))
    else:
        print("\t'%s'%s - FAILED ID: %d - %s" % (UnitTests[ID], " "*DupLen, ID, hex(Results[ID])))

RunnerSlot = 0
MaxRunnerSlots = min(32, multiprocessing.cpu_count() / 2)
RunnerSlots = [None] * MaxRunnerSlots
for RunnerID in range(UnitTestsSize):
    File = UnitTests[RunnerID]
    print("'%s' Running Test" % File)
    MaxFileNameStringLen = max(MaxFileNameStringLen, len(File))
    Threads[RunnerID] = Thread(target = Threaded_Manager, args = (sys.argv[1], RunnerID, File))
    Threads[RunnerID].start()
    if (MaxRunnerSlots != 0):
        RunnerSlots[RunnerSlot] = Threads[RunnerID]
        RunnerSlot += 1
        if (RunnerSlot == MaxRunnerSlots):
            for i in range(MaxRunnerSlots):
                RunnerSlots[i].join()
            RunnerSlot = 0

for i in range(UnitTestsSize):
    Threads[i].join()

print("====== PASSED RESULTS ======")
for i in range(UnitTestsSize):
    DupLen = MaxFileNameStringLen - len(UnitTests[i])
    if (Results[i] == 0):
        print("\t'%s'%s - PASSED ID: %d - 0" % (UnitTests[i], " "*DupLen, i))

print("====== FAILED RESULTS ======")
for i in range(UnitTestsSize):
    DupLen = MaxFileNameStringLen - len(UnitTests[i])
    if (Results[i] != 0):
        print("\t'%s'%s - FAILED ID: %d - %s" % (UnitTests[i], " "*DupLen, i, hex(Results[i])))
